using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace LateralProcesses
{
    /// <summary>
    /// This class handles parsing the input GRUinfo file and creating the required GRUs. It also provides
    /// a changelog of dates and specific GRUs that change during the simulation period.
    /// </summary>
    public class CDUBuilder
    {
        private string[] availableCDUClasses = new string[1] { "CDUHBVEC"};
        private List<CDUClass> CDUlist;        // List of GRU objects
        private string name = "";              // name of watershed
        private string CDUType = "";      // name of GRUclass to create
        private ArrayList columnNames;         // arraylist of column names 
        private string routing;
        private double timeStepHrs;
        private Dictionary<string, double> cduGlobalParameters;  
       
        #region CDUBuilder Properties
  
        /// <summary>
        /// The name of the watershed, or simulation
        /// </summary>
        public string SimulationName
        {
            get { return name; }
        }

        /// <summary>
        /// The name of CDUClass which has been instantiated
        /// </summary>
        public string CDUClassName
        {
            get { return CDUType; }
        }

        /// <summary>
        /// A list of all CDUs within the watershed simulation. Each element of the list is of CDUClass.
        /// </summary>
        public List<CDUClass> CDUs
        {
            get { return CDUlist; }
        }

        //overloaded indexers

        //allow for array type index

        public CDUClass this[int index]
        {
            get
            {
                return CDUlist[index];
            
            }        
        }

        //index on string (ID)
        private int findID(string ID)
        {
            for (int i = 0; i < CDUlist.Count; i++)
            {
                if (Convert.ToString(CDUlist[i].CDUID) == ID)
                {
                    return i;
                }
            }
           throw new Exception("CDU list does not contain this ID");
        }

        public CDUClass this[string index]
        {
            get
            {
                return this[findID(index)];
            }
        }

      


        #endregion

        public CDUBuilder(string CDUInfoFile, string routing, Dictionary<string,double> cduGlobalParameters, double timeStepHrs)
        {

            this.cduGlobalParameters = cduGlobalParameters;
            this.routing = routing;
            this.timeStepHrs = timeStepHrs;
            StreamReader srCduInfo = new StreamReader(CDUInfoFile);
            string cduContentInfo = srCduInfo.ReadToEnd();
            srCduInfo.Close();
            CDUlist = new List<CDUClass>();
            ParseCDUFile(cduContentInfo);
        }
        /// <summary>
        /// Get CDU IDs corresponding to order in CDU list
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, bool> GetIDs()
        {
            Dictionary<string, bool> IDs = new Dictionary<string, bool>();

              for (int i = 0; i < CDUlist.Count; i++)
              {
                  if (CDUlist[i].Write)
                      IDs.Add(CDUlist[i].CDUID, CDUlist[i].Write);
              }
          return IDs;
        }

        #region Private methods


        private void ParseCDUFile(string cduContentInfo)
        {
            Regex reg = new Regex(":CDUdescription\\s+", RegexOptions.IgnoreCase);
            string[] fileBlocks = reg.Split(cduContentInfo);
            if (fileBlocks.Length == 1) // split on :CDUdescription probably failed.
            {
                throw new Exception("CDU info file does not contain required information!");
            }
                       
            // now process the Header (the first element)
            parseHeader(fileBlocks[0]);

            // now parse and create GRUs
            createCDUs(fileBlocks[1]);
        }

        /// <summary>
        /// This routine handles parsing the header of a GRUinfo file
        /// </summary>
        /// <param name="headerBlock"></param>
        private void parseHeader(string headerBlock)
        {
            // split based on CRLF
            string[] lines = Regex.Split(headerBlock, "\\r\\n");
            foreach (string s in lines)
            {
                string stripped = stripComments(s);

                // split on colon
                string[] keyval = Regex.Split(stripped, ":");
                if (keyval.Length == 2)
                {
                    switch (keyval[0].Trim().ToLower())
                    {
                        case "name":
                            name = keyval[1].Trim();
                            break;
                        case "cdu class":
                            CDUType = keyval[1].Trim();
                            break;
                        case "column names": 
                            string[] colNames = Regex.Split(keyval[1].Trim().ToLower(), "\\s+");
                            columnNames = new ArrayList();
                            foreach (string n in colNames) columnNames.Add(n);
                            break;
                    }
                }
            }

            // check that the class name exists. This is a bit of a hack...a better 
            // approach would be to determine if the Class exists using Reflection...but 
            // I ain't that smart!!
            bool classFound = false;
            foreach (string name in availableCDUClasses)
            {
                if (name.ToLower() == CDUType.ToLower()) classFound = true;
            }

            if (!classFound)
            {
                throw new Exception(string.Format("CDU class {0} does not exist!", CDUType));
            }

            // now confirm that we have appropriate information from header
            if (columnNames.Count < 1)
            {
                throw new Exception("Error parsing header");
            }         
            
        }

        /// <summary>
        /// This routine reads the GRUdescription block and instantiates GRUs of the type
        /// represented in the header (_GRUClass)
        /// </summary>
        /// <param name="gruBlock"></param>
        private void createCDUs(string fileBlock)
        {
            string[] lines = Regex.Split(fileBlock, "\r\n");
            int ctr = 0;

            //instantiate properties

            foreach (string line in lines)
            {
                string l = stripComments(line).Trim();
                string[] cduInfo = Regex.Split(l, "\\s+");
                if (cduInfo.Length == columnNames.Count)
                {
                    CDUClass cdu = parseCDUClass(cduInfo);
                    CDUlist.Add(cdu);
                    ctr++;
                }
            }
        }

        /// <summary>
        /// This routine does the majority of the heavy-lifting in instantiating the GRUs
        /// </summary>
        /// <param name="gruInfo">An array from GRUinfo file representing GRU information</param>
        /// <returns>An instantiated CDUClass</returns>
       
        private CDUClass parseCDUClass(string[] cduInfo)
        {
            string id = "NULL";
            string idDown = "NULL";
            List<string> cduId = new List<string>();
            List<double> cduArea = new List<double>();
            double area = 0.0;
            double Z = 0.0;
            double slope = 0.0;
            double aspect = 0.0;
            bool output = false;
            Dictionary<string, double> cduLocalParameters = new Dictionary<string,double>();
            
            for (int i = 0; i < columnNames.Count; i++)
            {                
                string name = (string)columnNames[i];
                switch (name)
                {
                    case "id": id = Convert.ToString(cduInfo[i]); break;
                    case "iddown": idDown = Convert.ToString(cduInfo[i]); break;
                    case "gruid": string[] gruIdSt = Regex.Split(cduInfo[i], "/");
                        
                        foreach(string value in gruIdSt)
                        {
                            cduId.Add(value);
                        }
                        break;
                    case "gruarea": string[] gruAreaSt = Regex.Split(cduInfo[i], "/");

                        foreach (string value in gruAreaSt)
                        {
                            cduArea.Add(Convert.ToDouble(value));
                        }
                        break; 
                    case "area": area = Convert.ToDouble(cduInfo[i]); break;
                    case "slope": slope = Convert.ToDouble(cduInfo[i]); break;
                    case "z": Z = Convert.ToDouble(cduInfo[i]); break;
                    case "aspect": aspect = Convert.ToDouble(cduInfo[i]); break;
                    case "output": output = Convert.ToBoolean(cduInfo[i].ToLower()); break; 
                    default:
                        cduLocalParameters.Add(name, Convert.ToDouble(cduInfo[i]));
                        break;  
                }
            }

           
            // instantiate the class (this code MUST be modified as 
            // new CDU types are derived from CDUClass 
            CDUClass cdu;
            switch (CDUType.ToLower())
            {
                case "cduhbvec":
                    cdu = new CDUHBVEC(timeStepHrs, id, idDown, cduId, cduArea, area, Z, slope, aspect, routing, output,cduGlobalParameters, cduLocalParameters);
                    break;

                default:
                    // probably should not break here...but let's throw an error
                    throw new Exception("CDU does not exist!");
                    
            }

            return cdu;
        }

        /// <summary>
        /// A helper function which checks a line and truncates line at the 
        /// incidence of a comment character ("#")
        /// </summary>
        /// <param name="inputString">A string which may or may not contain comments</param>
        /// <returns>Truncated string, with comments removed</returns>
        private string stripComments(string inputString)
        
        {
            if (inputString.Length < 1) return inputString;

            // remove any comments (#) from line
            while (inputString.Contains("#"))
            {
                int pos = inputString.IndexOf("#");
                inputString = inputString.Substring(1, pos).Trim();
            }

            return inputString;
        }


        #endregion

          }
}
